package streaming;

import java.awt.Dimension;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Vector;

import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.Format;
import javax.media.Manager;
import javax.media.NoProcessorException;
import javax.media.NotConfiguredError;
import javax.media.NotRealizedError;
import javax.media.Player;
import javax.media.Processor;
import javax.media.ProcessorModel;
import javax.media.control.TrackControl;
import javax.media.format.AudioFormat;
import javax.media.format.UnsupportedFormatException;
import javax.media.format.VideoFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.SourceCloneable;
import javax.media.rtp.InvalidSessionAddressException;
import javax.media.rtp.Participant;
import javax.media.rtp.ReceiveStream;
import javax.media.rtp.ReceiveStreamListener;
import javax.media.rtp.RemoteListener;
import javax.media.rtp.SendStream;
import javax.media.rtp.SendStreamListener;
import javax.media.rtp.SessionAddress;
import javax.media.rtp.SessionListener;
import javax.media.rtp.event.ByeEvent;
import javax.media.rtp.event.NewParticipantEvent;
import javax.media.rtp.event.NewReceiveStreamEvent;
import javax.media.rtp.event.NewSendStreamEvent;
import javax.media.rtp.event.ReceiveStreamEvent;
import javax.media.rtp.event.ReceiverReportEvent;
import javax.media.rtp.event.RemoteCollisionEvent;
import javax.media.rtp.event.RemoteEvent;
import javax.media.rtp.event.SendStreamEvent;
import javax.media.rtp.event.SenderReportEvent;
import javax.media.rtp.event.SessionEvent;
import javax.media.rtp.event.StreamMappedEvent;
import javax.media.rtp.rtcp.ReceiverReport;
import javax.media.rtp.rtcp.SenderReport;
import javax.media.rtp.rtcp.SourceDescription;

import streaming.control.AudioControl;
import streaming.control.Control;
import streaming.control.VideoControl;
import utils.NetworkUtils;

import com.sun.media.rtp.RTPSessionMgr;

public class Conference implements ControllerListener, RemoteListener, SendStreamListener, ReceiveStreamListener, SessionListener {

	/**
	 * Indikator da je konferencija jednosmjerna, ka sagovorniku
	 */
	public static final int TO_PEER 		= 0x01;
	/**
	 * Indikator da je konferencija jednosmjerna, od sagovornika
	 */
	public static final int FROM_PEER 		= 0x10;
	/**
	 * Indikator da je konferencija dvosmjerna
	 */
	public static final int BOTH 			= 0x11;


	/**
	 * Indikator da je tip medija konferencije audio
	 */
	public static final int MEDIA_AUDIO 	= 0;
	/**
	 * Indikator da je tip medija konferencije video
	 */
	public static final int MEDIA_VIDEO		= 1;

	
	private static final Format FORMATS[] = {
		new AudioFormat(AudioFormat.LINEAR, 8000, 8, 1),
//		new VideoFormat(VideoFormat.H263),
		new VideoFormat(VideoFormat.H263, new Dimension(128, 96), 40960, byte[].class, 15)
	};

	private static final Format FORMATS_RTP[] = {
		new AudioFormat(AudioFormat.GSM_RTP, 8000, 8, 1),
		new VideoFormat(VideoFormat.H263_RTP)
	};

	private static boolean deviceFree[] = new boolean[]{true, true};

	private static InetAddress localAddress = NetworkUtils.getLocalAddress();
	private boolean sending = false;
	private boolean receiving = false;


	private SendStream sendStream;
	private ReceiveStream receiveStream;

	private int media;

	/**
	 * Status dostupnosti uredjaja
	 */
	private boolean available = false;

	private InetAddress destination;

	private Player monitor;

	private Processor captureProcessor;

	private Processor streamingProcessor;

	private RTPSessionMgr rtpManager;

	private DataSource streamingDataSource;

	private DataSource monitorDataSource;
	
	private Control control;
	private Control monitorControl;
//	private AudioControl audioControl = new AudioControl();
//	private AudioControl monitorAudioControl = new AudioControl();
//	
//	private VideoControl videoControl = new VideoControl(false);
//	private VideoControl monitorVideoControl = new VideoControl(true);

	/**
	 * 
	 * @param media je medija tip konferencije
	 */
	public Conference(int media) {
//		Manager.setHint(Manager.LIGHTWEIGHT_RENDERER, Boolean.TRUE);
		this.media = media;
		if(media == MEDIA_AUDIO){
			control = new AudioControl();
			monitorControl = new AudioControl();
		}
		else{
			control = new VideoControl(false);
			monitorControl = new VideoControl(true);
		}
		this.available = checkAvailability();
	}


	/**
	 * Provjerava dostupnost uredjaja za snimanje
	 * @return
	 */
	private boolean checkAvailability(){
		ProcessorModel model = new ProcessorModel(
				new Format[]{FORMATS[this.media]}, 
				new ContentDescriptor(ContentDescriptor.RAW));
		try {
			if(!deviceFree[media]) return false;
			captureProcessor = Manager.createRealizedProcessor(model);
//			System.out.println("processor started");
			deviceFree[media] = false;

			return true;
		} catch (Exception e) {
//			e.printStackTrace();
			System.out.println("Ne postoji uredjaj za prihvatanje " + (media == MEDIA_AUDIO?"audio":"video") + " sadrzaja.");
			return false;

		} 
	}
	
	/**
	 * Priprema izvore podataka za za monitor i streaming, ako je potrebno
	 * @throws NotRealizedError
	 * @throws IOException
	 * @throws NoProcessorException
	 * @throws NotConfiguredError
	 */
	private void generateProcessors() throws NotRealizedError, IOException,
	NoProcessorException, NotConfiguredError {
		captureProcessor.start();

		streamingDataSource = Manager.createCloneableDataSource(captureProcessor.getDataOutput());
		
		monitorDataSource =  ((SourceCloneable)streamingDataSource).createClone();

		streamingProcessor = Manager.createProcessor(streamingDataSource);

		StateHelper stateHelper = new StateHelper();
		streamingProcessor.addControllerListener(stateHelper);

		streamingProcessor.configure();
		stateHelper.waitForState(streamingProcessor, Processor.Configured);

		TrackControl[] tracks = streamingProcessor.getTrackControls();
		for(TrackControl track : tracks){
			if( track.setFormat(FORMATS_RTP[this.media]) == null ){
				track.setEnabled(false);
//				System.out.println("track: " + track  + " disabled.");
			}
//			System.out.println("track: " + track  + " enabled." + track.getFormat());
		}

		streamingProcessor.setContentDescriptor(new ContentDescriptor(ContentDescriptor.RAW_RTP));

		streamingProcessor.realize();
		stateHelper.waitForState(streamingProcessor, Processor.Realized);

		streamingProcessor.start();
		streamingDataSource = streamingProcessor.getDataOutput();
	}

	/**
	 * Inicijalizuje konferenciju. Pribavlja sve neophodne resurse,
	 * inicijalizuje i staruje RTP sesiju i registruje potrebne osluskivace
	 * @param destination Odredisna adresa
	 * @param localPort lokalni port
	 * @param remotePort port sagovornika
	 * @param status odredjuje tip konferencije (jednosmjerna, dvosmjerna)
	 * @param monitorOn indikuje da treba pokrenuti monitore
	 * @return true ako je inicijalizacija uspjesna
	 */
	public boolean initialize(InetAddress destination, int localPort, int remotePort, int status, boolean monitorOn){
		this.destination = destination;
//		this.localPort = localPort;
//		this.remotePort = remotePort;
		this.sending = ((status & TO_PEER) != 0) && isAvailable();
		
		this.receiving = (status & FROM_PEER) != 0;
//		this.monitorOn = monitorOn;
//		System.out.println(localPort + ":" + sending + ":" + receiving);
		try {
			generateProcessors();
		} catch (Exception e) {
			System.out.println("source taken....");
			
//			e.printStackTrace();
		} 

		if(isAvailable() && monitorOn){
			try {
//				if(media == MEDIA_AUDIO){
					monitorControl.setDataSource(monitorDataSource);
					monitor = monitorControl.getPlayer();
//				}
//				else{
//					monitorVideoControl.setMonitor(true);
//					monitorVideoControl.setDataSource(monitorDataSource);
//					monitor = monitorVideoControl.getPlayer();
//				}
				
				monitor.start();

				System.out.println("monitor started");

			} catch (Exception e) {
				e.printStackTrace();
				System.out.println("Cannot create monitor");
			} 

		}

		rtpManager = (RTPSessionMgr)RTPSessionMgr.newInstance();
		rtpManager.addSessionListener(this);
		if(receiving)
			rtpManager.addReceiveStreamListener(this);
		rtpManager.addSendStreamListener(this);
		rtpManager.addRemoteListener(this);
		try {
			rtpManager.initialize(new SessionAddress(localAddress, localPort));

			rtpManager.addFormat(FORMATS_RTP[this.media], media == MEDIA_AUDIO? 3: 34);

			rtpManager.addTarget(new SessionAddress(destination, remotePort) );

			System.out.println("Manager initialized");

			return true;
		} catch (InvalidSessionAddressException e) {
//			e.printStackTrace();
		} catch (IOException e) {
//			e.printStackTrace();
		}
		return false;

	}

	
	/**
	 * zapocinje se konferencija startovanjem odlaznog streaming toka
	 */
	public void startCall(){
		if(sendStream != null) return;
		if(sending){

			try {
				sendStream = rtpManager.createSendStream(streamingDataSource, 0);
				sendStream.start();
			} catch (UnsupportedFormatException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}


	}


	@Override
	public void update(RemoteEvent e) {

		if(e instanceof ReceiverReportEvent){
			ReceiverReportEvent event = (ReceiverReportEvent)e;
			ReceiverReport report = event.getReport();
//			if(report != null)
//				System.out.println(report.toString());
		}
		else if(e instanceof SenderReportEvent){
			SenderReportEvent event = (SenderReportEvent)e;
			SenderReport report = event.getReport();

		}
		else if(e instanceof RemoteCollisionEvent){
			RemoteCollisionEvent event = (RemoteCollisionEvent)e;
		}
	}


	@Override
	public void update(SendStreamEvent e) {
		if(e instanceof NewSendStreamEvent){
			System.out.println("NewSendStreamEvent");
		}
	}


	@Override
	public void update(ReceiveStreamEvent e) {
		Participant participant = e.getParticipant();	
		receiveStream = e.getReceiveStream(); 
		if(e instanceof NewReceiveStreamEvent){
			try {
				receiveStream = ((NewReceiveStreamEvent)e).getReceiveStream();
				DataSource ds = receiveStream.getDataSource();
//				System.out.println("ReceiveStreamEvent");
				// Find out the formats.
//				RTPControl ctl = (RTPControl)ds.getControl("javax.media.rtp.RTPControl");
//				if (ctl != null){
//				System.err.println("  - Recevied new RTP stream: " + ctl.getFormat());
//				} else
//				System.err.println("  - Recevied new RTP stream");

//				if (participant == null)
//				System.err.println("      The sender of this stream had yet to be identified.");
//				else {
//				System.err.println("      The stream comes from: " + participant.getCNAME()); 
//				}

				// create a player by passing datasource to the Media Manager
//				if(media == MEDIA_VIDEO){
//					videoControl.setMonitor(false);
//					videoControl.setDataSource(ds);
				System.out.println("Setting data source");
				control.setDataSource(ds);
//					JFrame frame = new JFrame("Stream");
//					frame.setBounds(0, 0, 320, 240);
//					frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
//					frame.getContentPane().add(videoControl.getVideoComponent());
//					frame.setVisible(true);
					
					
//				}
//				else{
//					audioControl.setDataSource(ds);
//					JFrame frame = new JFrame("Stream");
//					frame.setBounds(0, 0, 320, 240);
//					frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
//					frame.getContentPane().add(audioControl.getControlComponent());
//					frame.setVisible(true);
					
//				}
//				Player p = javax.media.Manager.createPlayer(ds);
//
//				if (p == null)
//					return;
//				StateHelper h = new StateHelper();
//				p.addControllerListener(h);
//				p.realize();
//				h.waitForState(p, Player.Realized);
//				System.out.println("realizing");

//				JFrame frame = new JFrame("Stream");
//				frame.setBounds(0, 0, 320, 240);
//				frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
//				JPanel panel = new JPanel();
////				if(media == MEDIA_AUDIO)
//					panel.add(vp.);
////				else
////					panel.add(p.getVisualComponent());
//
//				frame.setContentPane(panel);
//				p.start();
//				frame.setVisible(true);
				System.out.println("streaming started");
//				PlayerWindow pw = new PlayerWindow(p, stream);
//				playerWindows.addElement(pw);

				// Notify intialize() that a new stream had arrived.
//				synchronized (dataSync) {
//				dataReceived = true;
//				dataSync.notifyAll();
//				}

			} catch (Exception ex) {
				ex.printStackTrace();
				System.err.println("NewReceiveStreamEvent exception " + ex.getMessage());
				return;
			}
		}
		else if(e instanceof ByeEvent){
			ByeEvent event = (ByeEvent)e;
			rtpManager.closeSession("Bye event received.");
		}
		else if(e instanceof StreamMappedEvent){
			StreamMappedEvent event = (StreamMappedEvent)e;
			System.out.println("stream mapped to " + event.getParticipant().getCNAME());
		}

	}

//	public static void main(String[] args) {
//		Conference c1_a = new Conference(Conference.MEDIA_AUDIO);
////		Conference c1_v = new Conference(Conference.MEDIA_VIDEO);
//
////		Conference c2_a = new Conference(Conference.MEDIA_AUDIO);
////		Conference c2_v= new Conference(Conference.MEDIA_VIDEO);
//
//		try {
//			c1_a.initialize(InetAddress.getByName("192.168.1.3"), 22222, 22224, Conference.TO_PEER, true);
////			c1.initialize(NetworkUtils.getLocalAddress(), 22222, 22224, true, true, false);
////			c1.initialize(InetAddress.getByName("192.168.1.100"), 22222, 22224, true, false, false);
//			c1_a.startCall();
////			catch (Exception e) {}
////			c2_a.initialize(InetAddress.getByName("192.168.1.100"), 22224, 22222, false, true, false);
//
////			c1_v.initialize(InetAddress.getByName("192.168.1.100"), 22224, 22226, Conference.TO_PEER, true);
////			c1_v.startCall();
////			c1.initialize(NetworkUtils.getLocalAddress(), 22222, 22224, true, true, false);
////			c1.initialize(InetAddress.getByName("192.168.1.100"), 22222, 22224, true, false, false);
//
////			c2_v.initialize(InetAddress.getByName("192.168.1.3"), 22226, 22224, Conference.FROM_PEER, false);
////			c2_v.startCall();
//		} catch (Exception e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
//
//
//
//	}


	@Override
	public void update(SessionEvent e) {
		// TODO Auto-generated method stub
		if(e instanceof NewParticipantEvent){
//			NewParticipantEvent event = (NewParticipantEvent)e;
//			System.out.println(event.getParticipant().getCNAME());
//			Vector<SourceDescription> desc = event.getParticipant().getSourceDescription();
//			for(SourceDescription sd : desc){
//				System.out.println(sd.getDescription());
//			}
		}

	}


	@Override
	public void controllerUpdate(ControllerEvent event) {
		System.out.println(event);

	}


	/**
	 * 
	 * @return true ako je uredjaj za snimanje, pridruzen <code>Conference</code>
	 * objektu, dostupan
	 */
	public boolean isAvailable() {
		return available;
	}


	private void setAvailable(boolean available) {
		this.available = available;
	}


	/**
	 * poziva se pri prekidanju veze. Oslobadja sve resurse
	 */
	public void hangUp() {
		// TODO Auto-generated method stub
//		videoControl.stop();
//		monitorVideoControl.stop();
//		
//		audioControl.stop();
//		monitorAudioControl.stop();
		control.stop();
		monitorControl.stop();
		
		if(captureProcessor != null){
			captureProcessor.stop();
			captureProcessor.close();
//			captureProcessor.deallocate();
		}
		
		if(streamingProcessor != null){
			streamingProcessor.stop();
			streamingProcessor.close();
//			streamingProcessor.deallocate();
		}
		if(sendStream != null){
			sendStream.close();
			sendStream = null;
		}

		try{
			rtpManager.closeSession("sessionEnded");
		}
		catch(NullPointerException e){
			// ignore
		}

		deviceFree[media] = true;
		

	}


//	/**
//	 * 
//	 * @return <code>AudioControl</code> objekat prijemnog toka
//	 */
//	public AudioControl getAudioControl() {
//		return audioControl;
//	}
	
	/**
	 * 
	 * @return <code>AudioControl</code> objekat prijemnog toka
	 */
	public Control getControl() {
		return control;
	}
	
	/**
	 * 
	 * @return <code>Control</code> objekat monitora
	 */
	public Control getMonitorControl() {
		return monitorControl;
	}

//	public void setAudioControl(AudioControl audioControl) {
//		this.audioControl = audioControl;
//	}

//	/**
//	 * 
//	 * @return <code>AudioControl</code> objekat monitora
//	 */
//	public AudioControl getMonitorAudioControl() {
//		return monitorAudioControl;
//	}
//
//
//	public void setMonitorAudioControl(AudioControl monitorAudioControl) {
//		this.monitorAudioControl = monitorAudioControl;
//	}

//	/**
//	 * 
//	 * @return <code>VideoControl</code> objekat prijemnog toka
//	 */
//	public VideoControl getVideoControl() {
//		return videoControl;
//	}
//
//
//	public void setVideoControl(VideoControl videoControl) {
//		this.videoControl = videoControl;
//	}


//	/**
//	 * 
//	 * @return <code>VideoControl</code> objekat monitora
//	 */
//	public VideoControl getMonitorVideoControl() {
//		return monitorVideoControl;
//	}
//
//
//	public void setMonitorVideoControl(VideoControl monitorVideoControl) {
//		this.monitorVideoControl = monitorVideoControl;
//	}



	public static void main(String[] args) {
		long time = System.nanoTime();
		Conference c = new Conference(Conference.MEDIA_AUDIO);
		
		System.out.println(c.checkAvailability()); 
		System.out.println(System.nanoTime() - time);
	}

}
